package tuplespace.tuples;

/**
 * <p>Title: TupleSpace</p>
 * <p>Description: TupleSpace</p>
 * <p>Copyright: Copyright (c) 2002</p>
 * <p>Company: DII - DISMI</p>
 * @author Marco Mamei
 * @version 1.0
 */


import java.lang.reflect.*;
import java.util.*;
import emulator.*;
import emulator.utils.*;

import corepeerservices.tuples.*;


public class Tuple implements Cloneable
{
 /* this is the list of the tuples' fields not to be included in the match */
 private static final String[] NOMATCH = {"from"};
 /* this is the id of the tuple space from which the tuple came from */
 public String from;
 /* this is the tupe identifier, it is used for hash based matching */
 public String id;
 /* this is the content of the tuple, it is used for pattern based matching  */
 public String content;

 public Tuple()
 {
  id = "*";
  from = "*";
  content = "*";
 }

 public Tuple(String content)
 {
  id = "*";
  from = "*";
  setContent(content);
 }



 /*****************************************************************************/
 /*                     SET AND GET CONTENT METHODS                           */
 /*****************************************************************************/


 /* the content is something like;
 <field1=value1><field2=value2>...<fieldn=valuen>
 */
 public void setContent(String content)
 {
  String[] dataset = emulator.utils.StaticUtilities.splitMessage(content,'<','>');
  Field[] f = this.getClass().getFields();

  for(int i=0;i<dataset.length;i++)
  {
   int s = dataset[i].indexOf('=');
   String newfield = dataset[i].substring(0,s);
   String newvalue = dataset[i].substring(s+1);

   for(int j=0;j<f.length;j++)
   {
    String name = f[j].getName();
    if(name.equalsIgnoreCase(newfield))
    {
     try
     {
      String type = f[j].getType().getName();
      if(type.equals("java.lang.String"))
      {
       if(newvalue.equals("NULL"))
        f[j].set(this,null);
       else
        f[j].set(this,newvalue);
      }
      else if(type.equals("int"))
       f[j].setInt(this,Integer.parseInt(newvalue));
      else if(type.equals("float"))
       f[j].setFloat(this,Float.parseFloat(newvalue));
      break;
     }
     catch(IllegalAccessException iae)
     {
      iae.printStackTrace();
      System.exit(0);
     }
    }
   }
  }
 }

 /* this method serialized the content in something like:
 <field1=value1><field2=value2>...<fieldn=valuen>
 */
 public String getContent()
 {
  String flatContent = new String();
  try{
  Field[] f = this.getClass().getFields();
  for(int i=0;i<f.length;i++)
  {
   String name = f[i].getName();

   String fCont;
   if(f[i].get(this) != null)
    fCont = f[i].get(this).toString();
   else
    fCont = new String("NULL");

   flatContent = flatContent +"<"+name+"="+fCont+">";
  }
  } catch(Exception e){e.printStackTrace();}

  return flatContent.trim();
 }

 /*****************************************************************************/
 /*                     TUPLE MATCHING METHOD                                 */
 /*****************************************************************************/

 public boolean match (Tuple pattern)
 {
  /* two tuples match if one is assignable from the other and if their content
  match */
  if(!pattern.getTupleClass().isAssignableFrom(getTupleClass()))
   return false;

  if(this.id.equals("clock") && pattern.id.equals("clock"))
   return timeMatch(this.getContent(),pattern.getContent());
  else
   return contentMatch(this.getContent(),pattern.getContent());
 }

 /* QUESTO E' UN BRUTTO PEZZO DI CODICE...
 IL PROBLEMA E' CHE: MENTRE SI E' DECISO DI EVITARE IL CONFRONTO TRA
 NUMERI NEL MATCHING TRA TUPLE, IL CONFRONTO E' NECESSSARIO PER PER
 TUPLE CHE FANNO RIFERIMENTO AL CLOCK DI SISTEMA. */

 private boolean timeMatch(String content, String pattern)
 {
  /*
  content = <from=P73><id=clock><content=NULL><sensor=clock><value=79>
  pattern = <from=*><id=clock><content=NULL><sensor=clock><value=87>
  
  they match if the two values are equal, or if the pattern is a *
  */
	 
  
  
  String[] cont = StaticUtilities.splitMessage(content,'<','>');
  String[] patt = StaticUtilities.splitMessage(pattern,'<','>');
  
  System.out.println(cont[4] + " == " + patt[4]);
  
  return patt[4].equals("value=*") || cont[4].equals(patt[4]);
 }


 /* Here  the content and patterm come in the following form:
            CONTENT           VS.           PATTERN
 <from=*><id=*><content=ciao> VS. <from=*><id=*><content=ciao>
 */
 private boolean contentMatch(String content, String pattern)
 {
  String[] cont = StaticUtilities.splitMessage(content,'<','>');
  String[] patt = StaticUtilities.splitMessage(pattern,'<','>');

  /* in our definition, matching is 'pattern' included in 'content' */
  if(patt.length > cont.length)
   return false;  // this should never happen since content inherit from pattern

  String[][] cont_data = new String[cont.length][2];
  String[][] patt_data = new String[patt.length][2];

  loadTable(cont, cont_data);
  loadTable(patt, patt_data);

  for(int i=0;i<patt_data.length;i++)
  {
   String pF = patt_data[i][0];
   String pV = patt_data[i][1];

   if(pF == null)
    continue;

   boolean found = false;

   for(int j=0;j<cont_data.length;j++)
   {
    String cF = cont_data[j][0];
    String cV = cont_data[j][1];

    if(pF.equals(cF))
    {
     /* I found the field correspondence! */
     if(matchValueOfFiedls(cV,pV))
      found = true;
     break; // break out this cycle and go matching next patter field
    }

   }
   /* if I arrive here either */
   if(!found)
    return false;
  }
  /* if I arrive here all the pattern fields have been correctly matched */
  return true;
 }


 /* this method loads (as a side effect) the tables 'cont_data' and 'patt_data'
 with field=value couples */

 private void loadTable(String[] cont, String[][] cont_data)
 {
  /* load content table */
  for(int i=0;i<cont.length;i++)
  {
   int s = cont[i].indexOf('=');
   String f = cont[i].substring(0,s); // field
   String v = cont[i].substring(s+1); // value
   if(mustMatch(f))
   {
    cont_data[i][0] = f;
    cont_data[i][1] = v;
   }
  }
 }

 private boolean matchValueOfFiedls(String cVal, String pVal)
 {
  /* if either the cVal or the pVal is a wild-card, I can just return true */
  if(cVal.equals("*") || pVal.equals("*"))
   return true;

  /* if these are tuples I have to go on recursion, otherwise I can proceed
  with string match */
  if(cVal.startsWith("<") && pVal.startsWith("<"))
  {
   Tuple innerContent = Tuple.deserialize(cVal);
   Tuple innerPattern = Tuple.deserialize(pVal);
   if(innerContent.match(innerPattern))
    return true;
   else
    return false;
  }
  /* if are not tuples go with string match */
  else if(stringMatch(cVal,pVal))
        return true;
       else
        return false;
 }

 private boolean stringMatch(String c, String p)
 {
  if(c.equals(p) || (isNumber(c) && isNumber(p)))
   return true;
  else
   return false;
 }

 private boolean isNumber(String s)
 {
  char[] c = s.toCharArray();
  for(int i=0;i<c.length;i++)
   if ((c[i] < '0' || c[i] > '9') && c[i]!='-') return false;
  return true;
 }

 /* this method returns true if the string must be matched, it is not in the
 NOMATCH list */
 private boolean mustMatch(String field)
 {
  for(int i=0;i<NOMATCH.length;i++)
   if(field.equalsIgnoreCase(NOMATCH[i]))
    return false;
  return true;
 }


 /*****************************************************************************/
 /*                          LOW LEVEL METHODS                                */
 /*****************************************************************************/
 /* tuple equal method is based on their ids */
 public boolean equals (Object o)
 {
  if(o instanceof Tuple)
  {
   String thisId = this.id;
   String thatId = ((Tuple)o).id;
   return (thisId.equals( thatId ));
  }
  return false;
 }

 /* This method is a typed clone method */
 public Tuple duplicate()
 {
  try {
  return (Tuple)super.clone();
  }
  catch(Exception e)
  {e.printStackTrace(); return null;}
 }

 /* This method returns the name of the peer that orginally cerated the tuple,
 on the basis of its id */
 public String getSourceFromId()
 {
  if(id.equals("*"))
   return id;
  else
  {
   StringTokenizer st = new StringTokenizer(id,"-");
   return st.nextToken();
  }
 }

 public Class getTupleClass()
 {
  return getClass();
 }


 /*****************************************************************************/
 /*                 METHODS TO SERIALIZE / DESERIALIZE TUPLES                 */
 /*****************************************************************************/

 public String serialize()
 {
  String tupleTransferString = new String("<"+getTupleClass().getName()+
                                          "><"+getContent()+">");
  return tupleTransferString;
 }

 public static Tuple deserialize(String flatTuple)
 {
  String[] parsed = StaticUtilities.splitMessage(flatTuple,'<','>');
  String className = parsed[0];
  String content = parsed[1];
  try
  {
   Class clazz = Class.forName(className);
   Tuple t = (Tuple)clazz.newInstance();
   t.setContent(content);
   return t;
  }
  catch (Exception e)
  {
   System.out.println("tuple code not available...");
   e.printStackTrace();
   System.exit(-1);
   return null;
  }
 }

 public String toString()
 {
  return getContent();
 }

 /*****************************************************************************/
 /*                        MAIN FOR TESTING PURPOSES                          */
 /*****************************************************************************/
 /* main for testing purposes. It is used to evaluate the tuple match method */
 public static void main (String[] args)
 {
  /* correct match experiment
  Tuple t = new Tuple("<from=P3><id=id0><content=content0-3>");
  Tuple pattern = new Tuple("<from=*><id=*><content=content0-3>");
  System.out.println(t.getContent()+" VS. "+pattern.getContent());
  System.out.println(t.match(pattern));
  */

  /*
  tota.tuples.space.CoordTuple ct1 = new tota.tuples.space.CoordTuple();
  ct1.x = -5;
  tota.tuples.space.CoordTuple ct2 = new tota.tuples.space.CoordTuple();
  System.out.println(ct1.serialize());
  System.out.println(ct2.serialize());
  System.out.println(ct2.match(ct1));
  */

  /* external deserialization */
  //NetworkTuple nt = new NetworkTuple("<op=PC><ip=192.168.0.2>");
  //String flat = nt.serialize();
  //System.out.println(flat);
  //String flat = "[corepeerservices.tuples.NetworkTuple][<from=*><id=*><content=NULL><op=PC><ip=192.168.0.2>]";
  //Tuple t = Tuple.deserialize(flat);

  /* correct serialization experiment
  Tuple t = new Tuple("<content=ciao>");
  String flat = t.serialize();
  System.out.println(flat);
  Tuple cloneT = Tuple.deserialize(flat);
  System.out.println(cloneT.serialize()); */
 }
}